/**
 ** 公里标转换器：公里标与X坐标相互转换
 **
 ** 用法
 **   import miles from './miles.json'
 **   import MileConverter from './MileConverter'
 **   const mileConverter = new MileConverter(miles)
 **   const x = mileConverter.mileToX(mile)
 **   const mile = mileConverter.xToMile(x)
 */

type MileRecord = {
  x: number
  mile: number
  name: string
}

export default class MileConverter {
  miles: MileRecord[]
  constructor(miles: MileRecord[]) {
    this.miles = miles
  }

  // 公里标转三维X坐标
  mileToX(mile: number) {
    const min = this.miles[0].mile
    const max = this.miles[this.miles.length - 1].mile
    if (mile < min || mile > max) {
      const t = this.at(mile, min, max)
      return this.miles[0].x + (this.miles[this.miles.length - 1].x - this.miles[0].x) * t
    } else {
      const index = this.findIndexBinary(mile, 'mile')
      const min = this.miles[index].mile
      const max = this.miles[index + 1].mile
      const t = this.at(mile, min, max)
      return this.miles[index].x + (this.miles[index + 1].x - this.miles[index].x) * t
    }
  }

  // 三维X坐标转公里标
  xToMile(x: number) {
    const min = this.miles[0].x
    const max = this.miles[this.miles.length - 1].x
    if (x < min || x > max) {
      const t = this.at(x, min, max)
      return this.miles[0].mile + (this.miles[this.miles.length - 1].mile - this.miles[0].mile) * t
    } else {
      const index = this.findIndexBinary(x, 'x')
      const min = this.miles[index].x
      const max = this.miles[index + 1].x
      const t = this.at(x, min, max)
      return this.miles[index].mile + (this.miles[index + 1].mile - this.miles[index].mile) * t
    }
  }

  // 获取一个任意的公里标数值在哪个公里标记录的后面
  // 用二分搜索加速查找
  findIndexBinary(targetValue: number, propName: 'x' | 'mile') {
    if (targetValue < this.miles[0][propName] || targetValue > this.miles[this.miles.length - 1][propName]) {
      console.error('[MileConverter] 要查找的坐标超出索引')
      return 0
    }
    let startIndex = 0
    let endIndex = this.miles.length - 1
    while (endIndex - startIndex > 1) {
      const midIndex = Math.round((startIndex + endIndex) / 2)
      if (targetValue < this.miles[midIndex][propName]) endIndex = midIndex
      else startIndex = midIndex
    }
    return startIndex
  }

  // 计算归一化位置
  at(x: number, start: number, end: number) {
    return (x - start) / (end - start)
  }
}
